/*
 * Copyright (c) 2010, Johan T. Larsson All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Mercenary Commander project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *      
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL JOHAN T. LARSSON BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package games.TBSGame;

import java.util.ArrayList;
import java.util.Iterator;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.graphics.Point;
import android.opengl.GLSurfaceView.Renderer;
import android.os.Bundle;

public final class BattleRenderer implements Renderer {
		
	private class FloatPoint {
		public FloatPoint(float x, float y) {
			X = x;
			Y = y;
		}
		public float X, Y;
	}
	
	private ResourceManager ResourceManager;
	private BattleWorld World;
	
	private float ScaleX;
	private float ScaleY;
	private int ScreenX;
	private int ScreenY;
	private float Zoom = 0.1f;
	private float ZoomMin = 0.05f;
	private float ZoomMax = 0.5f;
	private float OffsetX = -5.0f;
	private float OffsetY = -5.0f;
	
	private float YMax;
	
	private Unit HeldUnit = null;
	private float HeldUnitPosX = 0;
	private float HeldUnitPosY = 0;
	
	private int UnitTexResource = R.drawable.units;
	private int UnitTexture = 0;
	
	private int TileTexResource = R.drawable.tilemap;
	private int TileTexture = 0;
	
	public Point SelectedTile;
	
	public boolean Initialized = false;
	public boolean TurnInProgress = false;
	
	public BattleRenderer(ResourceManager resourceManager, 
			BattleWorld world) {
		ResourceManager = resourceManager;
		World = world;
	}
	
	@Override
	public void onDrawFrame(GL10 gl) {
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
		
		gl.glColor4f(0, 0, 0, 1.0f);
		gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
        
        gl.glScalef(Zoom, Zoom, 1);
        float zI = 1.0f/Zoom;
        gl.glTranslatef(OffsetX + 0.5f*zI, OffsetY + 0.5f*zI, -3);
        
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
        
        synchronized(World)
        {
	        DrawGrid(gl);
	        DrawGoals(gl);
	        DrawUnits(gl);
	        DrawOrders(gl);
			if(HeldUnit != null)
			{
				gl.glPushMatrix();
				gl.glTranslatef(HeldUnitPosX, HeldUnitPosY, -0.5f);
				gl.glScalef(2.0f, 2.0f, 1.0f);
				gl.glTranslatef( - 0.5f, - 0.5f, 0);
				gl.glBindTexture(GL10.GL_TEXTURE_2D, UnitTexture);
				HeldUnit.RenderAtOrigo(gl);
				gl.glPopMatrix();
			}
			if(SelectedTile != null)
			{
				DrawSelection(gl);
			}
        }
	}
	
	public void DrawGoals(GL10 gl)
	{
		ArrayList<GoalTile> goals = World.Goals;
		Iterator<GoalTile> it = goals.iterator();
		GoalTile goal;
		while(it.hasNext())
		{
			goal = it.next();
			
			gl.glPushMatrix();
			if(goal.Y % 2 == 0)
				gl.glTranslatef(goal.X, goal.Y, 0);
			else
				gl.glTranslatef(goal.X + 0.5f, goal.Y, 0);
			
		    gl.glDisable(GL10.GL_TEXTURE_2D);
		    if(goal.Owner == 0)
		    	gl.glColor4f(0, 1, 0, 1);
		    else
		    	gl.glColor4f(1, 0, 0, 1);
		    gl.glLineWidth(5.0f);
		    
		    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ResourceManager.HexCoords);
	        gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, 6);
	        
	        gl.glLineWidth(1.0f);
		    gl.glEnable(GL10.GL_TEXTURE_2D);
			
			gl.glPopMatrix();
		}
	}
	
	public void DrawSelection(GL10 gl)
	{
		gl.glPushMatrix();
		if(SelectedTile.y % 2 == 0)
			gl.glTranslatef(SelectedTile.x, SelectedTile.y, 0);
		else
			gl.glTranslatef(SelectedTile.x + 0.5f, SelectedTile.y, 0);

	    gl.glDisable(GL10.GL_TEXTURE_2D);
	    gl.glColor4f(1, 0, 0, 1);
	    gl.glLineWidth(5.0f);
	    
	    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ResourceManager.HexCoords);
        gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, 6);
        
        gl.glLineWidth(1.0f);
	    gl.glEnable(GL10.GL_TEXTURE_2D);
		
		gl.glPopMatrix();
	}
	
	public void DrawUnits(GL10 gl)
	{
		ArrayList<Unit> units = World.VisibleUnits;
		Iterator<Unit> it = units.iterator();
		gl.glBindTexture(GL10.GL_TEXTURE_2D, UnitTexture);
		while(it.hasNext())
		{
			it.next().Render(gl);
		}
	}
	
	public void DrawOrders(GL10 gl)
	{
		ArrayList<Unit> units = World.VisibleUnits;
		Iterator<Unit> it = units.iterator();
		Unit unit;
		while(it.hasNext())
		{
			unit = it.next();
			if(unit.Team == World.LocalTeam)
			{
				unit.RenderOrders(gl);
			}
		}
	}

	public void DrawGrid(GL10 gl)
	{
		Point low, high;
		//synchronized(World)
		{
			FloatPoint point = ViewToRenderCoords(0, ScreenY);
			low = World.MapCoordinates(point.X, point.Y);
			point = ViewToRenderCoords(ScreenX, 0);
			high = World.MapCoordinates(point.X, point.Y);
			
			high.x = Math.min(high.x + 2, World.Size);
			high.y = Math.min(high.y + 2, World.Size);
			low.x = Math.max(0, low.x - 1);
			low.y = Math.max(0, low.y - 1);
		}
		
		gl.glBindTexture(GL10.GL_TEXTURE_2D, TileTexture);
		//TODO: Temporary way of doing things. Use sorting in future.
		for(int h = 0; h < 3; h += 1) {
			for(int i = low.x; i < high.x; ++i) {
				for(int j = low.y; j < high.y; ++j) {
					if(World.Landscape[i][j].Height != h)
						continue;
					
					gl.glPushMatrix();
	
					if(j % 2 == 0)
						gl.glTranslatef(i, j, World.Landscape[i][j].Height);
					else
						gl.glTranslatef(i + 0.5f, j, World.Landscape[i][j].Height);
	
					World.Landscape[i][j].Render(gl);
					gl.glPopMatrix();
				}
			}
		}
		
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ResourceManager.LargeSquareCoords);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, ResourceManager.SquareTexCoords);
        for(int j = high.y - 1; j >= low.y; --j) {
        	for(int i = low.x; i < high.x; ++i) {
				gl.glPushMatrix();

				if(j % 2 == 0)
					gl.glTranslatef(i, j, 0);
				else
					gl.glTranslatef(i + 0.5f, j, 0);

				World.Landscape[i][j].RenderOverlay(gl);
				gl.glPopMatrix();
			}
		}

        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ResourceManager.HexCoords);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, ResourceManager.HexTexCoords);
		gl.glDisable(GL10.GL_TEXTURE_2D);
		for(int i = low.x; i < high.x; ++i) {
			for(int j = low.y; j < high.y; ++j) {
				gl.glPushMatrix();

				if(j % 2 == 0)
					gl.glTranslatef(i, j, 0);
				else
					gl.glTranslatef(i + 0.5f, j, 0);

				if(!World.Landscape[i][j].Visible)
				{
					gl.glColor4f(0, 0, 0, 0.5f);
			        gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 6);
				}
				gl.glColor4f(0, 0, 0, 1);
		        gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, 6);

				gl.glPopMatrix();
			}
		}
		gl.glEnable(GL10.GL_TEXTURE_2D);
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		ScreenX = width;
		ScreenY = height;
		float w = 1;
		float h = (float)height/(float)width;
		if(w < h)
		{
			h = 1;
			w = (float)width/(float)height;
		}
		ScaleX = w/width;
		ScaleY = h/(float)height;
		
		gl.glViewport(0, 0, width, height);
		
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glOrthof(0, w, 0, h, 1, 10);
        
        YMax = h;
	}
	
	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		gl.glClearColor(0,0,0,1);
        gl.glShadeModel(GL10.GL_SMOOTH);
        gl.glDisable(GL10.GL_DEPTH_TEST);
        gl.glEnable(GL10.GL_TEXTURE_2D);
        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
        gl.glEnable(GL10.GL_BLEND);

        synchronized(World)
        {
        	World.onSurfaceCreated(gl);
        }
        if(TileTexture != 0) {
        	ResourceManager.DeleteTexture(gl, TileTexture);
        }
        TileTexture = ResourceManager.LoadTexture(gl, TileTexResource);
        if(UnitTexture != 0) {
        	ResourceManager.DeleteTexture(gl, UnitTexture);
        }
        UnitTexture = ResourceManager.LoadTexture(gl, UnitTexResource);
        
        Initialized = true;
	}
	
	private FloatPoint ViewToRenderCoords(float x, float y)
	{
		x *= ScaleX;
		y *= ScaleY;
		x = (x - 0.5f)/Zoom;
		y = (YMax - y - 0.5f)/Zoom;
		x -= OffsetX;
		y -= OffsetY;
		
		return new FloatPoint(x, y);
	}
	
	public void Click(float x, float y) {
		FloatPoint point = ViewToRenderCoords(x, y);
		synchronized(World)
		{
			Point p = World.MapCoordinates(point.X, point.Y);
			SelectedTile = p;
		}
	}

	public void Drag(float dX, float dY) {
		OffsetX += dX*ScaleX/Zoom;
		OffsetY -= dY*ScaleY/Zoom;
	}

	public void DragAndHold(float x, float y) {
		if(TurnInProgress)
			return;
		FloatPoint point = ViewToRenderCoords(x, y);
		HeldUnitPosX = point.X;
		HeldUnitPosY = point.Y;		
	}

	public void Drop(float x, float y, int mode) {
		if(HeldUnit == null || TurnInProgress)
			return;
		FloatPoint point = ViewToRenderCoords(x, y);
		synchronized(World)
		{
			Point p = World.MapCoordinates(point.X, point.Y);
			if(mode == 0)
				HeldUnit.OrderTo(p.x, p.y);
			else if(mode == 1)
				HeldUnit.Bombard(p.x, p.y);
		}
		HeldUnit = null;
		HeldUnitPosX = 0;
		HeldUnitPosY = 0;
	}

	public void Hold(float x, float y) {
		if(TurnInProgress)
			return;
		FloatPoint point = ViewToRenderCoords(x, y);
		synchronized(World)
		{
			Point p = World.MapCoordinates(point.X, point.Y);
			Unit unit = World.Landscape[p.x][p.y].Unit; 
			if(unit != null && unit.Team == World.LocalTeam)
			{
				HeldUnit = unit;
				
				HeldUnitPosX = point.X;
				HeldUnitPosY = point.Y;
			}
		}
	}
	
	public boolean Zoom(float amount) {
		Zoom *= amount;
		if(Zoom < ZoomMin) {
			Zoom = ZoomMin;
			return false;
		}
		if(Zoom > ZoomMax) {
			Zoom = ZoomMax;
			return false;
		}
		return true;
	}
	
	public void RestoreState(Bundle state)
	{
		Zoom = state.getFloat("UI_ZOOM");
		OffsetX = state.getFloat("UI_OFFSETX");
		OffsetY = state.getFloat("UI_OFFSETY");
	}
	
	public void SaveState(Bundle state)
	{
		state.putFloat("UI_ZOOM", Zoom);
		state.putFloat("UI_OFFSETX", OffsetX);
		state.putFloat("UI_OFFSETY", OffsetY);
	}
}
